<!DOCTYPE html>
<html>
<head>
    <style>
        * {
            box-sizing: border-box;
        }

        body {
            font-family: system-ui, -apple-system, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 1em;
            background: #f5f5f5;
        }
        
        .input-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            align-items: center;
        }

        .input-group textarea {
            flex-grow: 1;
            height: 2.8em;
            padding: 8px 12px;
            font-family: monospace;
            border: 2px solid #ccc;
            border-radius: 4px;
            resize: none;
        }

        .duration-input {
            width: 60px;
            height: 100%;
            padding: 8px;
            border: 2px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
        }

        .duration-label {
            font-size: 14px;
            color: #666;
            white-space: nowrap;
        }

        button {
            padding: 0 20px;
            background: #0066cc;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 600;
            height: 100%;
        }

        button:hover {
            background: #0052a3;
        }

        button:disabled {
            background: #cccccc;
            cursor: not-allowed;
        }
        
        .editor textarea {
            width: 100%;
            height: 150px;
            padding: 12px;
            margin-bottom: 20px;
            font-family: monospace;
            border: 2px solid #ccc;
            border-radius: 4px;
            resize: vertical;
        }
        
        #output {
            background: white;
            border: 2px solid #ddd;
            border-radius: 4px;
            padding: 20px;
            min-height: 300px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        #output svg {
            max-width: 100%;
            max-height: 260px;
        }
        
        .error {
            color: #cc0000;
            margin-top: 10px;
            font-size: 14px;
            min-height: 20px;
        }
        
        .label {
            font-weight: 600;
            margin-bottom: 8px;
            color: #333;
        }

        .progress {
            height: 4px;
            background: #eee;
            margin-bottom: 20px;
            border-radius: 2px;
            overflow: hidden;
        }

        .progress-bar {
            height: 100%;
            background: #0066cc;
            width: 0%;
            transition: width 0.1s ease-out;
        }

        #loadExampleLink {
            color: #0066cc;
            text-decoration: none;
            margin-right: 20px;
        }

        #loadExampleLink:hover {
            text-decoration: underline;
        }

        .top-controls {
            display: flex;
            align-items: center;
            margin-bottom: 8px;
        }
    </style>
<title>Progressively render SVG</title>
</head>
<body>
    <h1>Progressively render SVG</h1>
    <div class="top-controls">
        <a href="https://gist.githubusercontent.com/simonw/aedecb93564af13ac1596810d40cac3c/raw/83e7f3be5b65bba61124684700fa7925d37c36c3/tiger.svg" id="loadExampleLink">Load example image</a>
    </div>
    <div class="input-group">
        <textarea id="fullSvg" placeholder="Paste SVG here"></textarea>
        <input type="number" id="duration" class="duration-input" value="5" min="0.1" step="0.1">
        <span class="duration-label">seconds</span>
        <button id="renderBtn">Render</button>
    </div>
    <div class="progress">
        <div class="progress-bar" id="progressBar"></div>
    </div>

    <div class="label">Live editor:</div>
    <div class="editor">
        <textarea id="input" spellcheck="false"><svg><rect x="10" y="10" width="80" height="80" fill="blue"></rect><circle cx="120" cy="50" r="30" fill="red"</textarea>
    </div>
    
    <div class="label">Live Preview:</div>
    <div id="output"></div>
    <div id="error" class="error"></div>

    <script>
        function completeSVG(incompleteSVG) {
            if (!incompleteSVG || !incompleteSVG.trim()) {
                return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"></svg>';
            }

            const openTags = [];
            const tagRegex = /<\/?([a-zA-Z0-9]+)(\s+[^>]*)?>/g;
            let match;
            let currentPos = 0;
            let processedSVG = '';
            
            while ((match = tagRegex.exec(incompleteSVG)) !== null) {
                const fullTag = match[0];
                const tagName = match[1];
                const isClosingTag = fullTag.startsWith('</');
                
                processedSVG += incompleteSVG.slice(currentPos, match.index);
                processedSVG += fullTag;
                currentPos = tagRegex.lastIndex;
                
                if (!isClosingTag && !fullTag.endsWith('/>')) {
                    openTags.push(tagName);
                } else if (isClosingTag) {
                    if (openTags.length > 0 && openTags[openTags.length - 1] === tagName) {
                        openTags.pop();
                    }
                }
            }
            
            processedSVG += incompleteSVG.slice(currentPos);
            
            if (!incompleteSVG.includes('<svg')) {
                processedSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">' + processedSVG;
                openTags.unshift('svg');
            }
            
            while (openTags.length > 0) {
                const tagName = openTags.pop();
                processedSVG += `</${tagName}>`;
            }
            
            return processedSVG;
        }

        const input = document.getElementById('input');
        const fullSvg = document.getElementById('fullSvg');
        const duration = document.getElementById('duration');
        const renderBtn = document.getElementById('renderBtn');
        const output = document.getElementById('output');
        const error = document.getElementById('error');
        const progressBar = document.getElementById('progressBar');
        const loadExampleLink = document.getElementById('loadExampleLink');

        // Prevent editor updates during manual editing
        let isManualEdit = false;
        input.addEventListener('input', () => {
            if (!isManualEdit) {
                updateSVG();
            }
        });

        function updateSVG() {
            try {
                const completed = completeSVG(input.value);
                output.innerHTML = completed;
                error.textContent = '';
            } catch (e) {
                error.textContent = 'Error: ' + e.message;
            }
        }

        function animateSVG(svgString) {
            const durationMs = Math.max(100, duration.value * 1000); // Minimum 0.1 seconds
            const interval = 100; // 100ms steps
            const steps = durationMs / interval;
            let currentStep = 0;
            
            // Disable the render button during animation
            renderBtn.disabled = true;

            // Clear any existing animation
            if (window.currentAnimation) {
                clearInterval(window.currentAnimation);
            }

            window.currentAnimation = setInterval(() => {
                currentStep++;
                const progress = currentStep / steps;
                const charCount = Math.floor(svgString.length * progress);
                
                // Update progress bar
                progressBar.style.width = `${progress * 100}%`;

                // Get substring and complete it
                const partial = svgString.substring(0, charCount);
                const completed = completeSVG(partial);
                
                // Update both the preview and editor
                output.innerHTML = completed;
                isManualEdit = true; // Prevent recursive updates
                input.value = partial;
                
                // Scroll editor to bottom
                input.scrollTop = input.scrollHeight;
                
                isManualEdit = false;
                
                // End animation when complete
                if (currentStep >= steps) {
                    clearInterval(window.currentAnimation);
                    renderBtn.disabled = false;
                    progressBar.style.width = '0%';
                }
            }, interval);
        }

        // Handle paste and input events on the fullSvg textarea
        fullSvg.addEventListener('input', () => {
            // Clear the editor and preview
            input.value = '';
            output.innerHTML = '';
            error.textContent = '';
        });

        renderBtn.addEventListener('click', () => {
            if (fullSvg.value.trim()) {
                animateSVG(fullSvg.value);
            }
        });

        // Load example image
        loadExampleLink.addEventListener('click', async (e) => {
            e.preventDefault();
            try {
                const response = await fetch(loadExampleLink.href);
                if (!response.ok) throw new Error('Network response was not ok');
                const svgText = await response.text();
                fullSvg.value = svgText;
                error.textContent = '';
                // Clear the editor and preview
                input.value = '';
                output.innerHTML = '';
            } catch (error) {
                console.error('Error loading example:', error);
                error.textContent = 'Error loading example: ' + error.message;
            }
        });

        // Initial render
        updateSVG();

        // Prevent negative durations
        duration.addEventListener('input', () => {
            if (duration.value < 0.1) duration.value = 0.1;
        });
    </script>
</body>
</html>
